import logging
from django.conf import settings
from datetime import datetime
from pathos.multiprocessing import ProcessingPool as Pool
from common.timeutil import get_interval_end_pair
from django.core.management import BaseCommand
from common.const import IntervalType
from common.utils.maestro_exceptions import ExceptionType

_LOGGER = logging.getLogger(__name__)
_MAESTRO_LOGGER = logging.getLogger('maestro_script')


class OfflineCalculationHandler(BaseCommand):
    def __init__(self, process_no=4):
        super(OfflineCalculationHandler, self).__init__()
        self.pool = None
        self.base_time = None
        self.last_window = None

    def add_arguments(self, parser):
        parser.add_argument('--process_no', dest='process_no', type=int, default=4, help='number of process')
        parser.add_argument('--base_time', dest='base_time', help='yyyy-mm-dd HH:MM:SS')
        parser.add_argument('--last_window', dest='last_window', type=int, default=False,
                            help='current window or last window')
        parser.add_argument('--target_report', dest='target_report')
        parser.add_argument('--merchant', dest='merchant', help='tt|dark3|dark4|zs|loki|witch')
        parser.add_argument('--target_event', dest='target_event')
        parser.add_argument('--interval_type', dest='interval_type', help='valid value: hour|day|week|month|total')

    def check_merchant_start_time(self, interval_type, index, last_window):
        start_time, end_time = get_interval_end_pair(interval_type, self.base_time, last_window)
        index_start_time = settings.INDEX_START_DATE.get(index)
        if index_start_time and datetime.strptime(index_start_time, '%Y-%m-%d') > start_time:
            return False
        return True

    def get_window_interval(self, interval_type_value, day_to_calc=None):
        start_window = 0
        end_window = 1 if not self.last_window else self.last_window
        if self.last_window and interval_type_value == IntervalType.HOUR.value:
            end_window = (self.last_window - 1) * 24 + datetime.now().hour
        window_list = range(start_window, end_window + 1)
        if day_to_calc and interval_type_value == IntervalType.DAY.value:
            adjust_window_list = list()
            for window in window_list:
                for day_shift in day_to_calc:
                    adjust_window_list.append(window + day_shift)
            window_list = sorted(list(set(adjust_window_list)))
        return window_list

    @staticmethod
    def error_handler(index, event_name, interval_type, base_time, window):
        base_time, _ = get_interval_end_pair(interval_type, base_time, window)
        # error handling when es timeout.
        pass

    @staticmethod
    def track_event_execution(index, start_time, event_name, interval_type, base_time, window):
        # track to es
        end_time = datetime.now()
        base_time, _ = get_interval_end_pair(interval_type, base_time, window)
        track_data = dict(
            _id='-'.join([index, event_name, str(interval_type.value), str(base_time), str(start_time)]),
            _event_id='event_execution',
            index=index,
            event_name=event_name,
            interval_type=str(interval_type.value),
            base_time=str(base_time).split('.')[0],
            start_time=str(start_time).split('.')[0],
            end_time=str(end_time).split('.')[0],
            base_time_ts=base_time.timestamp(),
            start_time_ts=start_time.timestamp(),
            execution_time_ts=round(end_time.timestamp() - start_time.timestamp(), 2),
            _event_time=end_time.timestamp()
        )
        _MAESTRO_LOGGER.info(track_data)

    @staticmethod
    def start_execution(handler, interval_type, event_name, base_time, index, window, params):
        start_time = datetime.now()
        try:
            handler(**params)
            OfflineCalculationHandler.track_event_execution(index, start_time, event_name, interval_type, base_time,
                                                            window)
        except Exception as e:
            OfflineCalculationHandler.error_handler(index, event_name, interval_type, base_time, window)
            err_msg = "offline calculation exception, exception type: {exception_type}, fail to run: \n" \
                      "index: {index},\nevent_name: {event_name},\ninterval_type: {interval_type},\n" \
                      "base_time: {base_time}\n" \
                      "message: {message}\n".format(**{"index": index, "event_name": event_name,
                                                       "interval_type": interval_type.value,
                                                       "exception_type": ExceptionType.TG_ALARM.value,
                                                       "base_time": base_time,
                                                       "message": e})
            _LOGGER.error(err_msg)

    def handle(self, **kwargs):
        self.base_time = datetime.strptime(kwargs['base_time'], '%Y-%m-%d %H:%M:%S') \
            if kwargs['base_time'] else datetime.now()
        self.last_window = kwargs['last_window'] or 0
        self.pool = Pool(nodes=kwargs['process_no'])
